/**********************************************************************************
 *
 *
 * ngencontrol -- generator controller
 * v1.0a34 Aug 8, 2007 mjn -- put derivative code back to 1.0a30 version -- it was right before
 * 			divide integral portion by 32 instead of 4.  this gives us more 
 *			fractional resolution for the iterm.
 * v1.0a33 July 27, 2007 mjn -- modify stop1 to not change to run2, or monitor1
 *                              unless both rpm and oil pressure are above minimum
 * v1.0a32 july 20, 2007 mjn -- fix error in derivative portion of PID
 * v1.0a31 June 29, 2007 mjn -- first limited release of alpha level code
 * 
 * Copyright (c) 2007 Martin Nile
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * martin . nile @ gmail . com
 *
 *
 *
 *********************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
//#include <avr/sleep.h>	// handle CPU sleep (used in a/d conversion)
#include <avr/wdt.h>	// watch dog timer support
#include "ngencontrol.h"
#include <stdlib.h>		// we need this for the "abs" function

/**********************************************************************************
 *
 *
 * Generator Control Mainline
 *
 *
 *********************************************************************************/

int main( void )  {

	ports_init();	//Ports Initialization
	USART_Init(BAUD9600);   // set up the UART 9600 baud

	sei();           //Enable interrupts

	putstr("\r\nGencontrol 1.0a34\r\n");
	while(1) mainstate();
}

/**********************************************************************************
 *
 *
 * Initialization Code
 *
 *
 *********************************************************************************/
void ports_init(void)
{
	DDRB = PORTBDIR;
	DDRC = PORTCDIR;
	DDRD = PORTDDIR;

	PORTB = PORTBPULLUPS;
	PORTC = PORTCPULLUPS;
	PORTD = PORTDPULLUPS;

	// Timer/Counter 0. stepper motor clock
	// 8 bit, /256 prescale
	// timer is preloaded and started with each step of the motor
	TCCR0 = (1 << CS02);	// 62500 ticks/second
							// timer is not started until the stepper is energized


	// Timer/Counter 1. tachometer clock
	// 16 bit /256 prescale.  62500 ticks per second
	TCCR1A = 0;				// normal counter operation
	TCCR1B =  _BV(ICNC1)	// Input capture noise cancellation
			| (0 << ICES1)	// Input capture on falling edge
			| _BV(CS12);	// clk/256
	TIMSK |= (1 << TICIE1) |// Input Capture Interrupt Enable
			 (1 << TOIE1);	// Overflow Interrupt Enable

			
	// Timer Counter 2. realtime clock
	// 8 bit /256 prescale=62500 counts/sec. 250 count = realtime clock 250 interrupt / sec
	TCCR2 = (1 << WGM21) |	// clear timer on compare
			(1 << CS22) | 	// /256 prescale = 62500 counts/seconds
			(1 << CS21);
	OCR2 = REALTIMECLOCKTICKS;				// 62500/250 = 250 interrupts per second
	TIFR |= (1 << OCF2);	// enable output compare 
	TIMSK |= (1 << OCIE2);	// enable output compare interrupt.

	// set up MCU control register to enable sleep with ADC noise reduction mode
	// the cpu will be set to sleep mode immidately after initiating an a/d conversion
	// the cpu will wake up from sleep mode upon a/d interrupt, or timer interrupt.
	// MCUCR = 0b00101000;
	//MCUCR = (1 << SE)	|	// sleep 
	//		(1 << SM0);	//ACD noise reduction mode

	// enable the a/d converter with a/d clock=sysclock/128
	// we will be doing single conversions.  The conversion will be
	// intiated just before we want to read the adc
	ADCSRA = (1 << ADEN) | // analog to digital conversion enabled
			(0 << ADIE)	|	// a/d interrupt disabled
			(1<<ADPS2)| (1<<ADPS1)|(1<<ADPS0);	// a/d clock = sysclock/128

	// watchdog timer -- enable the timer with a 1 second timeout
	wdt_enable(WDTO_1S);
}

/*******************************************************
 * main state handler -- 
 *   update the clock
 *   gather the inputs
 *   update the display
 *   execute the appropriate state routine
 *   lather-rinse-repeat
 *******************************************************/

void mainstate(void) {
	setstate(STOP1);	// starts in the STOP1 state
	while(1) {
	    wdt_reset();	// reset the watchdog timer
	    stateruntime = seconds - statestarttime;
	    if(ESTOP) {      // a manaul emergency stop works no matter what state we are in
			errorstate=MANUALESTOP;
	       setstate(ESTOP1);
	    }

	    // read all of the input variables
	    getinput();

	    //update the display once per second or any time they push a button
	    if(seconds != displaytime || inputchar) {
	       displaytime=seconds;
	       updatedisplay();
	    }

	    switch(currentstate) {
	       case ESTOP1: {
	          estop1();
	          break;
	       }
	       case STOP1: {
	          stop1();
	          break;
	       }
	       case START1: {
	          start1();
	          break;
	       }
	       case START2: {
	          start2();
	          break;
	       }
	       case START3: {
	          start3();
	          break;
	       }
	       case START4: {
	          start4();
	          break;
	       }
	       case START5: {
	          start5();
	          break;
	       }
	       case START6: {
	          start6();
	          break;
	       }
	       case START7: {
	          start7();
	          break;
	       }
	       case START8: {
	          start8();
	          break;
	       }
	       case RUN1: {
	          run1();
	          break;
	       }
	       case RUN2: {
	          run2();
	          break;
	       }
	        case MONITOR1: {
	          monitor1();
	          break;
	       }
	       case MONITOR2: {
	          monitor2();
	          break;
	       }
	       case SHUTDOWN1: {
	          shutdown1();
	          break;
	       }
	       case SHUTDOWN2: {
	          shutdown2();
	          break;
	       }
	       case SHUTDOWN3: {
	          shutdown3();
	          break;
	       }
	       case STOP2: {
	          stop2();
	          break;
	       }
		   default: {
				errorstate=UNKNOWNSTATE;
			}
	    }
	}
}


/**********************************************************************************
 *
 *
 * USART -- serial communications
 * Uart routines borrowed from AVR306 application note 
 *
 *
 **********************************************************************************/
/* Initialize USART */
void USART_Init( unsigned int baudrate )
{
	/* Set the baud rate */
	UBRRH = (unsigned char) (baudrate>>8);                  
	UBRRL = (unsigned char) baudrate;
	
	/* Enable UART receiver and transmitter */
	UCSRB = ( ( 1 << RXCIE ) | ( 1 << RXEN ) | ( 1 << TXEN ) ); 
	
	/* Set frame format: 8 data 2 stop */
	UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)|(1<<USBS);
	
	/* Flush receive buffer */
	USART_RxTail = 0;
	USART_RxHead = 0;
	USART_TxTail = 0;
	USART_TxHead = 0;
}


/* Read and write functions */
// getch -- return th next serial character. 
// 			blocks until the character comes up
unsigned char getch( void )
{
	unsigned char tmptail;
	
	while ( USART_RxHead == USART_RxTail )  /* Wait for incomming data */
		;
	tmptail = ( USART_RxTail + 1 ) & USART_RX_BUFFER_MASK;/* Calculate buffer index */
	
	USART_RxTail = tmptail;                /* Store new index */
	
	return USART_RxBuf[tmptail];           /* Return data */
}

// putch -- print a character to the serial port.
// 			may block until there is room in the transmit buffer
void putch( unsigned char data )
{
	unsigned char tmphead;
	/* Calculate buffer index */
	tmphead = ( USART_TxHead + 1 ) & USART_TX_BUFFER_MASK; /* Wait for free space in buffer */
	while ( tmphead == USART_TxTail );

	USART_TxBuf[tmphead] = data;           /* Store data in buffer */
	USART_TxHead = tmphead;                /* Store new index */

	UCSRB |= (1<<UDRIE);                   /* Enable UDRE interrupt */
}

// testchar -- test to see if a character is ready to read
// 				return true if a character is ready, 
//				return false if nothing to read
unsigned char testchar( void )
{
	return ( USART_RxHead != USART_RxTail ); /* Return 0 (FALSE) if the receive buffer is empty */
}

/* putstr -- send a null terminated string to the serial console */
void putstr(char *str) {
	while(*str) {
		putch(*str);
		str++;
	}
}

/*--------------------------------------------------
 *  Display a 5 digit integer 0-65535 (16bit)
 *--------------------------------------------------*/
void print5(unsigned int x)
{
  unsigned int y;
  y=x/10000;putch(y+0x30);x-=(y*10000);
  y=x/1000;putch(y+0x30);x-=(y*1000);
  y=x/100;putch(y+0x30);x-=(y*100);
  y=x/10;putch(y+0x30);x-=(y*10);
  putch(x+0x30);
}
/*--------------------------------------------------
 *  Display a 4 digit integer 0-9999 (16bit)
 *--------------------------------------------------*/
void print4(unsigned int x)
{
  unsigned int y;
  y=x/1000;putch(y+0x30);x-=(y*1000);
  y=x/100;putch(y+0x30);x-=(y*100);
  y=x/10;putch(y+0x30);x-=(y*10);
  putch(x+0x30);
}
/*--------------------------------------------------
 *  Display a 3 digit integer 0-999 (16bit)
 *--------------------------------------------------*/
void print3(unsigned int x)
{
  unsigned int y;
  y=x/100;putch(y+0x30);x-=(y*100);
  y=x/10;putch(y+0x30);x-=(y*10);
  putch(x+0x30);
}



/**********************************************************************************
 *
 * Interrupt Service Routines
 *
 *********************************************************************************/
// Stepper Motor step clock
// Timer/Counter 0 overflow
// Interrupts when the counter overflows indicating it is time to move 
// If we hit one of the limit switches, stop all motion.
// if we have reached our target destination, then stop.
// otherwise, move to the next step, and reload the timer to interrupt us again
ISR(TIMER0_OVF_vect) {
	// if we are moving counterclockwise and we hit the lower limit 
	// (at the end away from the stepper motor, stop moving
	if(!(PINB & (1 << LIMITLO)) && steppertarget < steppercurrent) {
		steppercurrent=steppertarget=0;		
	}
	// or if we are moving clockwise and hit the upper limit, 
	else if (!(PINB & (1 << LIMITHI)) && steppertarget > steppercurrent) {
		steppertarget=steppercurrent;
	}

	if(steppertarget == steppercurrent) {
		PORTD &= ~((1 << STEPPER1) |		// Turn off all stepper coils to save power
					(1 << STEPPER2) |		//  (otherwise the limiting resistors get too hot)
					(1 << STEPPER3) |
					(1 << STEPPER4));
		TIMSK &= ~(1 << TOIE0);				// disable the stepper timer overflow interrupt;
	}
	else {
		// move to the next step 
		if(steppertarget < steppercurrent) {
			steppercurrent--;					// counter clockwise
			if(laststep-- == 0) laststep=3;
		}
		else {
			steppercurrent++;					// clockwise
			if(laststep++ == 3) laststep=0;
		}

		PORTD = stepmap[(int)laststep] | (PORTD & 0xf);
		TCNT0 = steppertime;			// reload the counter for the next step
	}
}



// Tachometer capture
// Timer Counter 1 Input Capture Interrupt
// whenever the tach pulse happens, the timer1 count is saved and
// the interrupt is invoked.  Subtract the previous reading from the
// current reading to get the total number of ticks between tach pulses.
ISR(TIMER1_CAPT_vect) {
	prevtachticks = tachticks;
	tachticks = (((long)tachoverflow << 16) + ICR1) - prevtach;
	prevtach= ICR1;
	tachoverflow=0;
}

// timer Counter 1 overflow interrupt
// the 16 bit counter overflows about once a second
ISR(TIMER1_OVF_vect) {
	tachoverflow++;
}

// realtime clock
// timer counter 2 output compare interrupt
ISR(TIMER2_COMP_vect) {
	// in CTC mode, the timer is cleared on compare, so we don't need to subtract.
	//TCNT2 -= (REALTIMECLOCKTICKS);

	if(++realtime >= 25) {		// every 25 ticks, bump the tenths counter
		if(++tenths >= 10) {	// every 10 times, bump the seconds counter
			tenths=0;
			seconds++;
		}
		realtime=0;
	}
	//if(++realtime >= 250) {		// after 250 ticks, bump the second counter by one
	//	realtime=0;
	//	seconds++;
	//}
}

/* USART Interrupt handlers */
// USART Receive Data Ready
ISR(USART_RXC_vect)
{
	unsigned char data;
	unsigned char tmphead;

	/* Read the received data */
	data = UDR;                 
	/* Calculate buffer index */
	tmphead = ( USART_RxHead + 1 ) & USART_RX_BUFFER_MASK;
	USART_RxHead = tmphead;      /* Store new index */

	if ( tmphead == USART_RxTail )
	{
		/* ERROR! Receive buffer overflow */
	}
	
	USART_RxBuf[tmphead] = data; /* Store received data in buffer */
}


// ISR(USART_TXC_vect)

// USART Data Register Empty 
ISR(USART_UDRE_vect)
{
	unsigned char tmptail;

	/* Check if all data is transmitted */
	if ( USART_TxHead != USART_TxTail )
	{
		/* Calculate buffer index */
		tmptail = ( USART_TxTail + 1 ) & USART_TX_BUFFER_MASK;
		USART_TxTail = tmptail;      /* Store new index */
	
		UDR = USART_TxBuf[tmptail];  /* Start transmition */
	}
	else
	{
		UCSRB &= ~(1<<UDRIE);         /* Disable UDRE interrupt */
	}
}

// the only purpose of the ADC interrupt handler is to provide a place
// for the cpu to awake from sleep mode.
EMPTY_INTERRUPT(ADC_vect);

/**********************************************************************************
 *
 *
 * Analog to digital conversion
 *
 *
 *********************************************************************************/



// start an analog to digital conversion
// sleep until the result is available
// return the answer.

// start and read the currently selected a/d port.
// the cpu is put into noise reduction sleep mode while the a/d runs
unsigned char readadc(void) {
	// start the conversion
	ADCSRA |= _BV(ADSC);

	// sleep until conversion completes
	//sleep_cpu();
	while(ADCSRA & (1 << ADSC));	// wait for the conversion to complete

	return(ADCH);
	
}



// read the analog to digital converter 
// channel = which ADC channel to read: 0=injector temp, 1=water temp, 2 = oil pressure
int getadc(unsigned char channel) {
	//int x;
	//int temperature;

	ADMUX =(0b11100000 | channel);	// use internal 2.56v ref,left adjusted, only read adch
	//ADMUX=0b11000000;	// internal 2.56v ref adc0 right adjusted, read adc

	readadc();	// toss out first conversion after changing channel
	return(readadc());

	//temperature=0;
	//for(x=0;x<=16;x++) {	// read 256 samples
	//	temperature+=readadc();
	//}
	//return(temperature >> 8);	//average 256 samples
}


// read the analog to digital converter  return a 10 bit value
// channel = which ADC channel to read: 0=injector temp, 1=water temp, 2 = oil pressure
int getadc10(unsigned char channel) {
	//int x;
	//int temperature;

	//ADMUX =(0b11100000 | channel);	// use internal 2.56v ref,left adjusted, only read adch
	ADMUX=(0b11000000 | channel);	// internal 2.56v ref adc0 right adjusted, read adc
	ADCSRA |= _BV(ADSC);

	// sleep until conversion completes
	//sleep_cpu();
	while(ADCSRA & (1 << ADSC));	// wait for the conversion to complete

	return(ADC);
}


/**********************************************************************************
 *
 * Stepper motor routines
 * 
 *********************************************************************************/

// setstepper -- set the speed control to a specific step count
// assumes that the steppercurrent has already been calibrated
void setstepper(int target) {
		steppertarget=target;
		if(TIMSK & (1 << TOIE0)) {	// stepper is already running
			// it just needs a new target 
			// don't force an immediate interrupt, because we will likely slip a step and 
			// eventually lose track of where we are
		}		
		else {	// stepper is stopped, enable interrupts and force first step
			TIMSK |= (1 << TOIE0);		// enable the stepper timer overflow interrupt
			TIFR |= (1 << TOV0);		// force an immediate overflow to start the motor
		}
}

// recalibrate -- move the stepper all the way back to zero
void recalibrate(void) {
	steppertarget=0;
	steppercurrent=4000;	// this is just random guess just so that the stepper goes towards zero

	TIMSK |= (1 << TOIE0);				// enable the stepper timer overflow interrupt
	TIFR |= (1 << TOV0);		// force an immediate interrupt to start the motor (possibly interrupting a step already in progress)
}
	

/**********************************************************************************
 *
 *
 * State machine routines
 *
 *
 *********************************************************************************/

/*******************************************************
 *   setstate -- change to the requested state
 *   "setstate" saves the requested state, and captures the runtime clock
 *   in the variable "statestarttime".  This is used as the reference point for
 *   states which require a delay of some sort.  The initstate flag is set for
 *   states which do initialization on the first invocation.
 *
 ******************************************************/
void setstate(unsigned char nextstate) {
putstr("\r\n");
putstr(statenames[currentstate]);
putstr("->");
    currentstate=nextstate;
putstr(statenames[currentstate]);
putstr("\r\n");
    statestarttime=seconds;   // timestamp of when the state started
    initstate=1;
	timeout=0;
}

/*******************************************************
 * stop1 -- main entry point for the state system
 ******************************************************/
void stop1(void) {
    // if the run signal is present, and we already have oil pressure, then
    // we have probably just been reset while the engine was already running
    // switch to RUN state after a short delay to allow the tach to get a
    // reading on the current RPM.
    if(RUN && oilpressure >= minoilpressure && rpm > startrpm) {
       setstate(RUN2);
    }

    // if we have the run signal and no oil pressure, then start the engine
    if(RUN && oilpressure < minoilpressure && rpm == 0) {
       setstate(START1);
       crankretries=0;
    }

    // if we have oil pressure, but no run signal, then the engine has been started
    // manually.  Change to the monitor mode after a short delay to allow the tach
    // to catch up.
    if(STOP && oilpressure >= minoilpressure && rpm > startrpm) {
       setstate(MONITOR1);
    }

    // otherwise keep waiting in stop1 state
}


/*******************************************************
 * start1 -- turn on the glow plugs and wait for glowplugdelay
 ******************************************************/
void start1(void) {
    if(initstate) {      // first time, turn on the glow plug
       PORTC |= (1 << GLOWPLUG);
	   recalibrate();
       initstate=0;
	   timeout=seconds+glowplugdelay;
    }

    // if we get a stop signal, then turn off the glowplugs and go back to stop state
    if(STOP) {
       PORTC &= ~(1 << GLOWPLUG);       // glow plug off
        setstate(SHUTDOWN2);
    }
      
    // if we have reached the glow plug timeout, and the stepper has
	// finished the recalibrate, then turn off
    // the glow plug and move to the next state
    else if(stateruntime >= glowplugdelay && steppercurrent ==0) {
       PORTC &= ~(1 << GLOWPLUG);       // glow plug off
       setstate(START2);
    }

    // otherwise keep waiting until the glow plug timeout
}

/*******************************************************
 * start2 -- pull the decompression lever, and start cranking
 ******************************************************/
void start2(void) {
    if(initstate) {
       PORTB |= (1 << DECOMPRESS) | (1 << STARTOUT);   // pull the decompression lever and engage the starter
       initstate=0;
       setstepper(startspeed);   // set the speed control to "start"
	   timeout=seconds+decompressdelay;
    }

	// If the run signal goes away, close the decompression lever
	// and shut down.
	if(STOP) {
		PORTB &= ~(1 << DECOMPRESS);
		setstate(SHUTDOWN2);
	}

    // keep going until the decompression timeout happens
    if(stateruntime >= decompressdelay) {
        PORTB &= ~(1 << DECOMPRESS);    // drop the decompression lever, keep cranking
        setstate(START3);
    }
}

/*******************************************************
 * start3 -- main cranking state
 ******************************************************/
void start3(void) {
	if(initstate) {
		timeout=seconds+cranktimelimit;
		initstate=0;
	}
    // if the run signal goes away, then do a shutdown
    if(STOP) {
       PORTB &= ~(1 << STARTOUT);      // stop cranking
       setstate(SHUTDOWN2);
    }

    // if we have been cranking too long, stop cranking and go to take a break
    else if(stateruntime > cranktimelimit) {
       PORTB &= ~(1 << STARTOUT);      // stop cranking

       // if we have retried too many times, stop altogether
       if(crankretries++ > crankretrylimit) {
          errorstate=CRANKRETRYERROR;
          setstate(STOP2);
       }
       // otherwise, delay and try again
       else {
           setstate(START6);
       }
    }

    // if we are cranking too slowly, then error
    else if(rpm <= mincrankspeed) {
       PORTB &= ~(1 << STARTOUT);      // stop cranking
       errorstate=SLOWCRANKERROR;
       setstate(STOP2);                        // cranking too slow error
    }

    // if the rpm is above startrpm, then the engine has started
    else if(rpm > startrpm) {
       PORTB &= ~(1 << STARTOUT);      // stop cranking
       setstate(START4);                     // go wait for the oil pressure to build up
    }

    // otherwise, keep cranking
}

/*******************************************************
 * start4 -- wait for the oil pressure to come up
 ******************************************************/
void start4(void) {
	if(initstate) {
		timeout=seconds+oilpressuretimeout;
		initstate=0;
	}
    // if the run signal goes away, then do a shutdown
    if(STOP) {
       setstate(SHUTDOWN2);
    }

    // if we dont have oil pressure by a certain amount of time, then
    // do a panic shutdown.
    else if(oilpressure < minoilpressure && stateruntime > oilpressuretimeout) {
        errorstate=LOWOIL;
        setstate(ESTOP1);  
    }
    // if the oil pressure reaches the minimum (+ 2psi hysterisis), move on to warming up the engine
    else if(oilpressure >= minoilpressure+2) {
       setstate(START5);      // warm up the engine
    }
	// if the rpm drops while waiting for oil pressure, delay and try starting again
	else if(rpm < startrpm) {
		setstate(START8);
	}
}

/*******************************************************
 * checkestop -- check all variables which could cause an estop
 ******************************************************/
unsigned char checkestop() {

	if(rpm > maxrpm) errorstate=OVERSPEED;
	if(oilpressure < minoilpressure) errorstate=LOWOIL;
    if(watertemp > maxwatertemp) errorstate=OVERTEMP;
    if(ESTOP) errorstate=MANUALESTOP;
	// if the oil pressure is 255, that indicates that we have lost the oil
	// pressure sender.
	if(oilpressure == 255) errorstate=OILLOSTERROR;
    // If we don't have an rpm reading, then we must have lost the tach sensor.
    // Treat this as an emergency stop situation
    // (the rpm sensor had to be working for us to even get to this state, so
    // something is definitely wrong);
    if(rpm == 0) errorstate=TACHLOSTERROR;
	
	return(errorstate);
}


/*******************************************************
 * start5 -- warm up the engine
 ******************************************************/
void start5(void) {
	if(initstate) {
		timeout=seconds+warmuptimeout;
		initstate=0;
	}
    if(checkestop()) {      // check everything which could cause an emergency stop
       setstate(ESTOP1);
    }

    // if the run signal goes away, then do a shutdown
    if(STOP) {
       setstate(SHUTDOWN2);
    }

    else if(stateruntime > warmuptimeout) {
		// let the pid controller in the run1 state handle getting us to speed
       // setstepper(runspeed);   // set the speed control to "run speed"
       setstate(RUN1);         // we are now fully running
    }
	// if the engine stops while waiting for warmup, try to restart
	else if(rpm < startrpm) {
		setstate(START8);
	}
}

/*******************************************************
 * start6 -- overcrank delay.
 *  We end up here if we cranked the engine too long without starting
 *  delay for a while to let the starter cool down.  Then go try starting again
 ******************************************************/
void start6(void) {
	if(initstate) {
		timeout=seconds+overcranktimeout;
		initstate=0;
	}
	if(STOP) {
		setstate(SHUTDOWN2);
	}
    else if(stateruntime > overcranktimeout) {
       setstate(START1);   // if we have waited long enough, go try starting again
    }
}

/*******************************************************
 * start7 -- restart the engine after it stopped in run state
 * this state pauses to ensure the engine is really stopped 
 * after rpm and oil pressure drop, the state moves to start1
 * (or error if restart attempts exceeded)
 * this differs from the start8 state in that start8 goes back to start5 
 ******************************************************/
 void start7(void) {
	if(STOP) {
		setstate(SHUTDOWN2);
	}
	if(ESTOP) {	// manual estop is the only emergency stop supported in this state
		setstate(ESTOP1);
	}

	// when the engine is fully stopped (no rpm and no oil pressure)
	// increment our start attempt counter, and go try to restart
	if(rpm == 0 && oilpressure < minoilpressure) {
		// if we exceed the restart limit, then stop with error
		if(++restartattempts > restartretrylimit) {
			errorstate=RESTARTATTEMPTS;
			setstate(STOP2);
		}
		// otherwise, go try starting the engine again.
		else {
			setstate(START1);
		}
	}

	// if the engine somehow restarts itself while we are waiting for oil pressure
	// to drop, then switch back to run state.
	if(rpm > startrpm && oilpressure > minoilpressure) {
		setstate(RUN1);	
	}
	// otherwise, keep waiting for the engine to stop
}

/*******************************************************
 * start8 -- restart the engine after it stopped while waiting
 * for oil pressure to build up.
 * this state pauses to ensure the engine is really stopped 
 * after rpm and oil pressure drop, the state moves to start1
 * (or error if restart attempts exceeded)
 * if the engine restarts itself, the state moves back to start5
 ******************************************************/
void start8(void) {
	if(STOP) {
		setstate(SHUTDOWN2);
	}
	// when the engine is fully stopped (no rpm and no oil pressure)
	// increment our start attempt counter, and go try to restart
	if(rpm == 0 && oilpressure < minoilpressure) {
		if(++restartattempts > restartretrylimit) {
			errorstate=RESTARTATTEMPTS;
			setstate(STOP2);
		}
		else {
			setstate(START1);
		}
	}

	// if the engine somehow restarts itself while we are waiting for it to stop
	// then switch back to run state.
	if(rpm > startrpm && oilpressure > minoilpressure) {
		setstate(START5);	
	}
	// otherwise, keep waiting for the engine to stop
}


/*******************************************************
 * run1 -- the main run state -- monitor a bunch of things
 ******************************************************/
void run1(void) {
    if(checkestop()) {      // check everything which could cause an emergency stop
       setstate(ESTOP1);
    }

    // if the turn off the "run" switch, then to a clean shutdown
    else if(STOP) {
       setstate(SHUTDOWN1);
    }

#ifndef PIDTUNE	// PID setup uses uco and diesel empty as a/d ports
    // if we are runing on UCO, and the UCO tank goes empty
    // switch to diesel and raise an error.
    else if(RUNNINGONUCO && UCOEMPTY) {
       PORTD &= ~(1 << UCOVALVEBIT);   // switch to diesel
       errorstate=UCOEMPTYERROR;
    }

    // if we run out of diesel (even if we are running on UCO),
    // switch to UCO and do a purge/shutdown
    else if(DIESELEMPTY) {
       PORTD &= ~(1 << UCOVALVEBIT);   // switch to diesel
       errorstate=DIESELEMPTYERROR;
       setstate(SHUTDOWN1);
    }

    // if we are running on diesel, and the injector line is up to the minimum,
    // and the water temperature is at the minimum, then
    // switch to UCO fuel (as long as there is UCO in the tank).
    else if(RUNNINGONDIESEL && !UCOEMPTY && injtemp >= mininjtemp && watertemp >= minwatertemp) {
       PORTD |= (1 << UCOVALVEBIT);      // switch to UCO
    }
#endif

    // If we are running on UCO and the injector line, or water temp drops
    // below the minimum, then switch back to diesel.
    //   note: the minimum temps have 2 degrees of hysterisis to prevent
    //   oscillation around the switchover temperature
    else if(RUNNINGONUCO && injtemp < (mininjtemp-2) && watertemp < (minwatertemp-2)) {
       PORTD &= (1 << UCOVALVEBIT);      // switch to DIESEL;
    }

	// if the engine speed drops below the start rpm, make sure it really is stopped,
	// then try restarting
	else if(rpm < startrpm) {
		setstate(START7);
	}

	// every tenth of a second, calculate any adjustment to the speed
	if(pidtenths != tenths) {
		pidtenths = tenths;
		setstepper(steppercurrent + pidcontroller());
	}
}	


/*******************************************************
 * run2 -- delay for up to 1 second for the rpm reading to show up
 * we get to this state if the controller wakes up and finds that the
 * engine is already running, (oil pressure) and the "run" signal is present.  We delay
 * temporarily to give the tach a chance to start registering (0 rpm is
 * cause for an ESTOP when in RUN1 state.
 * The most likely reason to be in this state is that the engine was
 * started manually (because of a dead battery).
 * It is remotely possible to end up here after a watchdog timeout.
 * The watchdog timer is reset each time through the main state loop.  If the
 * watchdog timer runs out, then it generates a reset signal to start over.
 *
 * as of version 1.0a33 this state is not needed, because we don't leave
 * the stop1 state until both the oil pressure and rpm are reading
 * above minimum.  For now, this state just falls through to run1.
 ******************************************************/
void run2(void) {
    if(rpm >= startrpm ) {
       setstate(RUN1);         // we are now fully running
   }

    // if we wait more than 1 second for an rpm reading to show up, then
    // we have something wrong (we have oil pressure and a run signal, but no rpm).
    else if(stateruntime > 1) {
       errorstate=TACHLOSTERROR;
       setstate(ESTOP1);
    }
}

/*******************************************************
 * monitor1 -- delay for up to 1 second for rpm before switching to monitor mode
 * monitor mode is an emergency fall back mode.  If the battery is dead,
 * the controller is also dead.  The engine can be manually started without the
 * controller.  Once the engine starts, it begins to create power and the
 * controller will likely wake up.  If the controller wakes up and finds
 * that there is oil pressure (which will come up long before the generator) then
 * the controller assumes the engine was started manually.   In monitor mode
 * the controller will only monitor the critical engine variables. 
 *
 * as of version 1.0a33 this state is not needed, because we don't
 * change out of stop1 state until after rpm and oil pressure are
 * both above minimums.  For now, it just falls through to monitor2.
 ******************************************************/
void monitor1(void) {
    if(rpm >= startrpm ) {
       setstate(MONITOR2);         // the engine is running, just monitor
	}

	// if the oil pressure disappears while we are waiting for the rpm,
	// then go back to the stopped state (this sometimes happened during
	// testing after a power on reset.  The adc sometimes picked up  noise
	// which made it look like we had oil pressure.  This prevents ending up
	// in estop state caused by a short glitch in the oil pressure sensor reading
	else if(oilpressure < minoilpressure) {
   		setstate(STOP1);
	}
	// if the oil pressure is 255, that indicates that we have lost the oil
	// pressure sender.
	else if(oilpressure == 255) {
		errorstate=OILLOSTERROR;
		setstate(ESTOP1);
	}

    // If we wait more than 1 second for an rpm reading to show up, then
    // we have something wrong.  (If the engine is running it should take
    // 1/30th of a second to get our first rpm reading.
    // we have oil pressure, no run signal, but no rpm).
    else if(stateruntime > 1) {
       errorstate=TACHLOSTERROR;
       setstate(ESTOP1);
    }
}

/*******************************************************
 * monitor2 -- monitor the critical inputs, but don't do anything else
 *                 We get here by manually starting the engine without
 *                  turning on the "run" signal.
 ******************************************************/
void monitor2(void) {
    if(checkestop()) {      // check everything which could cause an emergency stop
       setstate(ESTOP1);
    }

    else if(RUN) {            // if they turn on the "run" switch, then change to full run mode
       setstate(RUN1);
    }
	else if(rpm < startrpm) {	// manual shut down
		setstate(STOP1);
	}
}


/*******************************************************
 * shutdown1  -- switch from UCO to diesel, and wait for purge timeout
 ******************************************************/
void shutdown1(void) {
    if(initstate) {
		initstate=0;
	   timeout=seconds+purgetimeout;

		if(RUNNINGONDIESEL) {		// if already running on diesel, skip the purge
			setstate(SHUTDOWN2);
		}
       PORTD &= ~(1 << UCOVALVEBIT);      // switch to DIESEL;
    }
    if(checkestop()) {      // check everything which could cause an emergency stop
       setstate(ESTOP1);
    }

    else if(stateruntime > purgetimeout) {
       setstate(SHUTDOWN2);
    }
}


/*******************************************************
 * shutdown2 -- set the speed control to zero, wait for rotation to stop
 ******************************************************/
void shutdown2(void) {
    if(initstate) {
       recalibrate();	// force the stepper to zero speed
       initstate=0;
	   timeout=seconds+shutdowntimeout;
    }

    // when the speed hits zero, and the oil pressure drops below the minimum,
    // then we know for certain the engine has stopped.
    if(rpm == 0 && oilpressure < minoilpressure) {
       setstate(SHUTDOWN3);
    }

    // if it takes too long to shutdown, then do an estop
    else if(stateruntime > shutdowntimeout) {
       errorstate=SHUTDOWNTIMEOUTERROR;
       setstate(ESTOP1);
    }
}

/*******************************************************
 * shutdown3 -- the engine has stopped, put the speed control back to run.
 * Note:  This is a useless state, because unless the design gets changed,
 * we will  lose power soon unless the 12 volt supply is plugged into
 * something which supplies power.  To make this step useful, the system
 * system must be redesigned to have battery power available at all times.
 * In order to do that, we will need to redesign for minimum power consumption
 ******************************************************/
void shutdown3(void) {
	if(initstate) {
		recalibrate();
		initstate=0;
	}
	if(steppercurrent == 0) {	// wait for the recalibrate to finish
    	setstepper(runspeed);
    	setstate(STOP2);
	}
}

/*******************************************************
 * stop2 -- wait for the stop signal to appear before switching to full stop mode
 *             this prevents the system from oscillating between run-estop-start-run-estop etc...
 ******************************************************/
void stop2(void) {
	if(rpm > 0 || oilpressure > minoilpressure) {
		setstate(SHUTDOWN1);
	}
	else if(STOP) {
		errorstate=0;
		setstate(STOP1);
    }
}

/*******************************************************
 * estop1 -- EMERGENCY STOP
 *   shut down everything and pull the plug
 ******************************************************/
void estop1(void) {
    if(initstate) {
        // turn off glow plug
        PORTC &= ~(1 << GLOWPLUG);

        // stop cranking
        PORTB &= ~(1 << STARTOUT);

        // turn off UCO valve
        PORTD &= ~(1 << UCOVALVEBIT);

        // set the speed control to zero
        setstepper(-4000);	// force the speed fully off

        // pull the decompression lever
        PORTB |= (1 << DECOMPRESS);
       initstate=0;
    }

    // keep holding the decompression lever until all rotation stops
    // and the oil pressure drops below the minimum.  After that,
    // we can go to the stop2 state and wait for the user to
    // turn off the "run" switch.
    if(rpm == 0 && oilpressure < minoilpressure) {
       PORTB &= ~(1 << DECOMPRESS);       // close the decompression lever
       setstate(SHUTDOWN3);
    }
}

/*******************************************************
 * getinput -- read all of the engine variables
 ******************************************************/
void getinput(void){
	long x;

	injtemp=getadc(0);		// ADC channel 0 = injector line temperature
#ifndef PIDTUNE
	watertemp=getadc(1);	// ADC channel 1 = water temperature
#else
	watertemp=20;
#endif
	oilpressure=getadc(2);	// ADC channel 2 = oil pressure (this will need to be tweaked to convert volts to PSI)

	if(oilpressure < 255) {	// 255 indicates loss of oil pressure sender
		oilpressure-= 19;	// by sheer coincidence, other than a 19psi offset, the volts read directly as psi
	}

	// calculate the current RPM based on the number of timer ticks for the last revolution
	// calculate a moving average of the last two rotations because there is
	// only one power pulse every two rotations.  Only average the rpm when in run1 mode
	// The earlier states sometimes get an obviously wrong rpm reading because prevtachticsk
	// may have happened during shutdown where it gets
	if(currentstate == RUN1) {
		x=(tachticks+prevtachticks)/2;
	}
	else {
		x=tachticks;
	}
								// tachticks is set by the tach interrupt service routine
								// this is the number of timer ticks in the last revolution

	// the tach timer runs at 3750000 ticks per minute
	// divide ticks/minute by ticks/rotation gives rotations/minute
	rpm=(int)((long)3750000/x);
	if(tachoverflow > 1) {
		rpm=0;
	}
	if(testchar()) {
		inputchar=getch();
	}
}


/*******************************************************
 * updatedisplay -- show the current state information
 * If an error is pending, alternate the current state 
 * display with the error display every other second
 ******************************************************/

void updatedisplay(void ) {
	// if they typed anything, see if they want to speed up, or slow down
	if(inputchar == '+') {
		setstepper(steppercurrent + 10);
	}
	else if(inputchar == '-') {
		setstepper(steppercurrent - 10);
	}
	else if(inputchar == ',' || inputchar == '<') {
		setstepper(steppercurrent -1);
	}
	else if(inputchar == '.' || inputchar == '>') {
		setstepper(steppercurrent +1);
	}
	else if(inputchar == 'R' || inputchar == 'r') {
		recalibrate();
	}

	inputchar = 0;

	//1234567890123456
	//state name
	//error name
	//timeout xxxx    
	//Wnnn Onnn Innn  V  <--- water temp, oil pressure, Injector temp, Fuel (Diesel/Vegoil)
	//Rnnnn Sxxxx Tnnnn  <--- RPM, stepper, time
 
	putstr(statenames[currentstate]);	// first line (possibly blank)
	if(errorstate) {
		putstr(errornames[errorstate]);
	}
	else if(timeout > seconds) {
		putstr("Timeout    ");
		print4(timeout - seconds);
	}
	putch('W');print3(watertemp);putch(' ');	// second line (1st if state is blank)
	putch('O');print3(oilpressure);putch(' ');
	putch('I');print3(injtemp);putch(' ');
	if(RUNNINGONDIESEL) putch('D');
	else putch('V');
	putch('R');print4(rpm);putch(' ');			// third line (2nd if state is blank)
	putch('S');print4(steppercurrent),putch(' ');
	putch('T');print4(seconds);

	/* 
	putstr(", RPM=");
	print4(rpm);

	putstr(", Inj =");
	print3(injtemp);

	putstr(", Water=");
	print3(watertemp);

	putstr(", Oil=");
	print3(oilpressure);

	putstr(", Step=");
	print4(steppercurrent);

	putstr(errornames[errorstate]);
	*/

	putch('\r');
}


         

/*******************************************************
 * pidcontroller -- calculate the adjustment to the stepper position
 *
 * based on emprical tests: Pc=20, Kc=280
 * 
 * Ziegler-Nichols tuning: pgain=0.65 * Kc  = 182
 *                               Ti=0.5 * Pc = 10
 *                              Td=0.12 * Pc = 2.4
 *                         dgain= pgain*Td = 182*2.4=436
 *                         igain= pgain/Ti = 18 *4 = 72
  * All gain values are multiplied by 256 so we can do
 * the arithmetic in integers.  The result is divided by 256
 * 
 ******************************************************/
int pidcontroller(void) {
	int error;
	long pterm;		// proportional term.  The pterm is long because on startup
					// the pterm has a very large error when multiplied by the pgain
					// will exceed the size of a normal int.
	int iterm;		// integral term
	int dterm;		// derivative term

	int temp;


#ifdef PIDTUNE
	pgain=(getadc10(3)-8)*2;	// pc3 (uco empty) is temporarily used as a pgain control
	if(pgain < 0) pgain=0;
	igain=getadc(4)-8;		// pc4 (diesel empty) is used as igain control
	if(igain < 0) igain = 0;
	dgain=(getadc10(1)-8)*4;	// pc1 (watertemp) is dgain control
	if(dgain < 0) dgain = 0;
#endif


	error=TARGETRPM-rpm;

	// calculate the proportional portion
	pterm=((long)error*pgain);


	// calculate the integral portion
	// If we are at the stepper limit, and still want to go higher,
	// do not add any more to the integral
	if(!(PINB & LIMITHI) && pterm > 0) {
		temp=sumerror;
	}
	else {
		temp = sumerror + error;
	}

	if(temp > 5000){		// limit the integral windup
		temp=5000;
	}
	else if(temp < -5000){
 		temp=-5000;
	}

	sumerror = temp;
	iterm = (igain * sumerror)/32;	// note!! /32 gives us more fractional resolution for the igain variable

	// Calculate the derivative portion
	dterm = dgain *  (rpm - lastrpm);
	lastrpm = rpm;

	temp = (pterm + iterm + dterm) / 256;

	// if the adjustment is less than 2 steps, don't bother
	if(abs(temp) < 2) { 
		temp=0; 
	}

#ifdef PIDTUNE
	putstr("\r\n");
	print4(rpm);
	putch(' ');
	print3(pgain);
	putch(' ');
	print3(igain);
	putch(' ');
	print3(dgain);
	putch(' ');
	print5(temp);
#endif
	return(temp);
}
